package com.fx.securityConfig;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fx.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import java.io.PrintWriter;

/**
 * des:
 *
 * @author fxiao
 * @date 2020/12/9 13:16
 */
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserService userService;

    /**
     * PasswordEncoder是一个专门用于处理加密和解密的接口，这里需要为其指定一个实现类
     *
     * @return
     */
    @Bean
    PasswordEncoder passwordEncoder() {
        PasswordEncoder passwordEncoder=null;
//        passwordEncoder=NoOpPasswordEncoder.getInstance();//没有任何编码的明文方式
        /**
         * security 官方推荐的加密方式，这里的参数是加密强度，可以不写，默认是10，范围是4-31
         */
        passwordEncoder=new BCryptPasswordEncoder(10);//
        return passwordEncoder;
    }


    /**
     * 将我们的userService implements UserDetailsService 传给 security
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService);
    }

    /**
     * 配置配置角色的继承关系
     * @return
     */
    @Bean
    RoleHierarchy roleHierarchy(){
        RoleHierarchyImpl hierarchy=new RoleHierarchyImpl();
        hierarchy.setHierarchy("ROLE_admin > ROLE_user");
        return hierarchy;
    }
/*
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                // 基于url的授权规则
                // 如果要访问这个路径（/admin/**）下的资源，需要有admin这个角色（这里的角色不要加“ROLE_”这个前缀）
                //  .antMatchers("/admin/**").hasRole("admin")
                //  .antMatchers("/user/**").hasRole("user")
                .anyRequest().authenticated()//除了上面的规则以外其余所有链接只需要在认证之后就能访问。anyRequest需要在所有的antMatchers之后，否则会报错
                .and()
                //登录表单
                .formLogin()
                .loginProcessingUrl("/dologin")
                // 这里设置登录成功后的回调，我们在这里修改从而达到返回json的目的
                // req是HttpServletRequest,resp是HttpServletResponse,authentication是当前登录成功后的用户信息
                .successHandler((req, resp, authentication) -> {
                    resp.setContentType("application/json;charset=utf-8");
                    PrintWriter out = resp.getWriter();
                    out.write(new ObjectMapper().writeValueAsString(authentication.getPrincipal()));
                    out.flush();
                    out.close();
                })
                //这是登陆失败的回调
                .failureHandler((req, resp, exception) -> {
                    resp.setContentType("application/json;charset=utf-8");
                    PrintWriter out = resp.getWriter();
                    String message = "";
                    if (exception instanceof LockedException) {
                        message = "账户被锁定，请联系管理员!";
                    } else if (exception instanceof CredentialsExpiredException) {
                        message = "密码过期，请联系管理员!";
                    } else if (exception instanceof AccountExpiredException) {
                        message = "账户过期，请联系管理员!";
                    } else if (exception instanceof DisabledException) {
                        message = "账户被禁用，请联系管理员!";
                    } else if (exception instanceof BadCredentialsException) {
                        message = "用户名或者密码输入错误，请重新输入!";
                    }
                    out.write(new ObjectMapper().writeValueAsString(message));
                    out.flush();
                    out.close();

                })
                .permitAll()
                .and()
                .logout()
                .logoutUrl("/logout")
                //登出成功的回调，anthentication是用户信息
                .logoutSuccessHandler((req,resp,anthentication)->{
                    resp.setContentType("application/json;charset=utf-8");
                    PrintWriter out = resp.getWriter();
                    out.write(new ObjectMapper().writeValueAsString("注销登录成功"));
                    out.flush();
                    out.close();
                })
                .permitAll()
                .and()
                //关闭csrf跨域攻击防御
                .csrf().disable()
                //未认证回调
                .exceptionHandling()
                .authenticationEntryPoint((req, resp, exception) -> {
                    resp.setContentType("application/json;charset=utf-8");
                    PrintWriter out = resp.getWriter();
                    out.write(new ObjectMapper().writeValueAsString(exception.getMessage()+"尚未登录，请登录"));
                    out.flush();
                    out.close();
                })
        ;
    }
*/

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                //"/login"这个地址可以随意访问
                .antMatchers(HttpMethod.POST, "/login").permitAll()
                //其他地址需要登录后访问
                .anyRequest().authenticated()
                .and()
                .addFilterBefore(new JwtLoginFilter("/login",authenticationManager()), UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(new JwtFilter(),UsernamePasswordAuthenticationFilter.class)


                //关闭csrf跨域攻击防御
                .csrf().disable()
                //未认证回调
                .exceptionHandling()
                .authenticationEntryPoint((req, resp, exception) -> {
                    resp.setContentType("application/json;charset=utf-8");
                    PrintWriter out = resp.getWriter();
                    out.write(new ObjectMapper().writeValueAsString(exception.getMessage()+"尚未登录，请登录"));
                    out.flush();
                    out.close();
                })

        ;
    }

}
